/*
 * TMap Designer - A Thematic Map design software
 * Copyright (C) 2006 World Health Organization
 * http://www.who.int
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */
package who.tmap.imp.impl;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;

import who.tmap.bo.AttrDef;
import who.tmap.bo.Attrs;
import who.tmap.bo.impl.BizObjectFactory;
import who.tmap.common.TMapException;
import who.tmap.imp.CSVImportAdapter;
import who.tmap.imp.ImportAdapterException;

/**
 * 
 * @author stl
 *
 * A CSV adapter to import comma or tab separated text file
 * */
public class CSVImportAdapterImpl extends BaseImportAdapterImpl 
								  implements CSVImportAdapter {
    
    private static Logger logger = Logger.getLogger(CSVImportAdapterImpl.class);	
	
    protected String fileName;
    protected String separator;
    protected String textQualifier; 
    protected boolean firstRowContainsFieldNames;
    
    protected BufferedReader in; 
    
    public CSVImportAdapterImpl() {
        setName( "CSV" );
    }

    public void configure(Map<String, String> config) throws ImportAdapterException {
    	this.config =config;
    	config.put( DS_TYPE, CSVImportAdapter.class.getName() );
    	
        fileName =(String)config.get( FILE_NAME );
        separator =(String)config.get( SEPARATOR );
        textQualifier =(String)config.get( TEXT_QUALIFIER );
        firstRowContainsFieldNames =Boolean.parseBoolean( config.get( FIRST_ROW_CONTAINS_FIELD_NAMES ) );
    }
    
    /**
     * Open text file for input
     */
    public void connect() throws ImportAdapterException {
        try {
            //in = new BufferedReader(new FileReader(fileName));
        	in = new BufferedReader(
                    new InputStreamReader( new FileInputStream( fileName ), "UTF8" ) );
        } catch( Exception e  ) {
            throw new ImportAdapterException( e );
        }
    }

    /**
     * Close text file
     */
    public void disconnect() throws TMapException {
        try {
            in.close();
        } catch( Exception e ) {
            throw new ImportAdapterException( e );
        }
    }
    
    /**
     * 
     */
    public Collection<AttrDef> readHeader() throws TMapException {
       List<AttrDef> header =(List<AttrDef>) readHeaderFieldNames();
       
       String dataLine;
       
       try {
           dataLine =readLine();
       } catch( IOException e ) {
           throw new ImportAdapterException( e );
       }
       
       assignHeaderTypes( header, dataLine );
       
       return header;
    }
    
    /**
     * First line of the file contains the field names
     * But we need to read a data line to extract the data types
     */
    @SuppressWarnings("unchecked")
	private List<AttrDef> readHeaderFieldNames() throws ImportAdapterException {
        List<AttrDef> fieldsDef =new ArrayList<AttrDef>();
        
        try {    
            String inputLine =readLine();
            
            List fields =(List)buildRow( inputLine, -1 );
             
            AttrDef attrDef;            
            for( int ct =0; ct < fields.size(); ct++ ) {
                attrDef = (AttrDef)boFactory.get( BizObjectFactory.ATTR_DEF );
                
                attrDef.setName( (String)fields.get( ct ) );
                // By default, we set all the fields to string
                attrDef.setType( DEFAULT_FIELD_TYPE );
                
                fieldsDef.add( attrDef );
            }
            
        } catch (Exception e) {
            throw new ImportAdapterException( e );
        }
         
        return fieldsDef;
    }
    
    /**
     * Extract the header types
     * @param header The header info read from the 1st line
     * @param dataLine A line of data
     * @throws TMapException
     */
    @SuppressWarnings("unchecked")
	private void assignHeaderTypes( Collection<AttrDef> header, String dataLine ) throws TMapException {
        String[] fd = dataLine.split(separator);
        AttrDef attrDef =null;
        
        for( int ct =0; ct < fd.length; ct++ ) {
            
            Object value =getObjectValue( fd[ ct ] );
            
            if ( header.size() > ct ) {
                attrDef =(AttrDef)((ArrayList)header).get( ct );
            } else {
                attrDef =(AttrDef)boFactory.get( BizObjectFactory.ATTR_DEF );                
                header.add( attrDef );
            }
            
            attrDef.setType( value.getClass().getName() );
            
            // Assign a default name to empty field names
            if ( ( attrDef.getName() == null ) || ( attrDef.getName().equals( "" ) ) ) {
                attrDef.setName( DEFAULT_FIELD_NAME + ( ct + 1 ));
            }
        }        
    }
    
    /**
     * Read one line of the text file
     * @return the read line
     * @throws IOException
     */
    private String readLine() throws IOException {
        return in.readLine();
    }
      
    /**
     * Convert a String to an object value (Integer, Double or String)
     * @param in
     * @return
     */
    private Object getObjectValue( String in ) {
        Object value;
        
        try {
            value =new Integer( in );
        } catch( Exception e ) {
            
            try {
                value =new Double( in );
            } catch( Exception e2 ) {

                try {
                    value =new String( in );
                } catch( Exception e3 ) {
                    value =new String();
                }
                
            }
        }
        
        return value;
    }
    
/*    private Object getNullValue( String className ) {
    	Object val;
    	if ( Integer.class.getName().equals( className ) ) {
    		val =new Integer(0);
    	} else if ( Double.class.getName().equals( className ) ) {
    		val =new Double(0);
    	} else {
    		val =new String();
    	}
    	
    	val =null;
    	return val;
    }*/
    
    @SuppressWarnings("unchecked")
	private List buildRow( String inputLine, int nbCol ) {
        List<Object> fields =new ArrayList<Object>();        
        
        String[] fd;
        
        if ( nbCol == -1 )  {
        	fd = inputLine.split(separator);
        } else {
        	fd = inputLine.split(separator, nbCol);
        }
        
        for( int ct =0; ct < fd.length; ct++ ) {
            
            Object value =getObjectValue( fd[ ct ] );
                       
            fields.add( value );
        }
        
        return fields;
    }
    
    @SuppressWarnings("unchecked")
	protected Attrs readValues(int nbRec ) throws ImportAdapterException {
        Attrs attrs =null;        
        List<List> rows =new ArrayList<List>();
        List<AttrDef> header =null;
        
        int recCt =0;
        String inputLine ="";
                
        try {  
            attrs =(Attrs)boFactory.get( BizObjectFactory.ATTRS );
            attrs.setRows( rows );                 
            
            if ( firstRowContainsFieldNames ) {
                header =readHeaderFieldNames();
            } else {
                header =new ArrayList<AttrDef>();
            }
            
            attrs.setHeader( header );            
            
            while ( ( nbRec == -1 ) || ( recCt < nbRec ) ) {

                recCt++;
                
                try {
                    inputLine =readLine();
                    
                    if ( inputLine != null ) {
                    
                        List row =buildRow( inputLine, header.size()  );
                    
                        rows.add( row );
                        
                        // We need to read the line of data to get the header data type
                        // We use the 1st one we read
                        if ( recCt == 1 ) {
                            assignHeaderTypes( header, inputLine );
                        } /*else {
                        	// In case the last field is null, 
                        	// as there is no ending separator for the last field
                        	if ( row.size() < header.size() ) {
                        		AttrDef lastAttrDef =header.get( header.size() - 1 );
                        		String type =lastAttrDef.getType();
                        		Object val =getNullValue( type );
                        		row.add(val);
                        	}
                        }*/
                        
                    } else {
                        break;
                    }
                    
                } catch( IOException ioe ) {
                    break;
                }
       
            }
       
            attrs.setDsProperties( config );
        } catch (Exception e) {
            throw new ImportAdapterException( e );
        }
        
        return attrs;
                
    }
    
}
